Skip to content

Conversation

@vector-one
Copy link
Contributor

Fixes #750
Implements derivative of ellipsoid-based fluid forces with respect to velocities. Includes all component derivatives:

  • Added mass forces
  • Viscous torque and drag
  • Kutta lift
  • Magnus force

The implementation adds the fluid force derivatives to qDeriv, following the pattern from the reference C implementation.

Implements derivative of ellipsoid-based fluid forces with respect to velocities.
Includes all component derivatives:
- Added mass forces
- Viscous torque and drag
- Kutta lift
- Magnus force

The implementation adds the fluid force derivatives to qDeriv, following the
pattern from the reference C implementation.
@thowell thowell self-requested a review December 11, 2025 16:29


@wp.kernel
def _qderiv_ellipsoid_fluid(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets rename to _deriv_ellipsoid_fluid



@wp.kernel
def _qderiv_ellipsoid_fluid(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make this a wp.func instead of a wp.kernel. additionally, it should have worldid and bodyid inputs.

continue

size = geom_size[worldid % geom_size.shape[0], geomid]
semiaxes = _geom_semiaxes_deriv(size, geom_type[geomid])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets import _geom_semiaxes from passive.py. additionally let's change the name (in passive.py) to geom_semiaxes

slender_drag_coef = geom_fluid[geomid, 2]
ang_drag_coef = geom_fluid[geomid, 3]

if density > 0.0 and magnus_coef != 0.0:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets structure this with one if density > 0.0 check and checks for magnus_coef, kutta_coef and viscosity in the density check scope.



@wp.func
def _pow2_deriv(val: float) -> float:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is unused, lets remove it

lin_vel = wp.spatial_bottom(local_vels)
ang_vel = wp.spatial_top(local_vels)

virtual_lin_mom = wp.vec3(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fluid_density * wp.cw_mul(virtual_mass, lin_vel)

fluid_density * virtual_mass[1] * lin_vel[1],
fluid_density * virtual_mass[2] * lin_vel[2]
)
virtual_ang_mom = wp.vec3(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fluid_density * wp.cw_mul(virtual_inertia, ang_vel)

ang_drag_coef * II[2] + slender_drag_coef * (I_max - II[2])
)

mom_visc = wp.vec3(x * mom_coef[0], y * mom_coef[1], z * mom_coef[2])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wp.cw_mul(ang, mom_coef)


mom_visc = wp.vec3(x * mom_coef[0], y * mom_coef[1], z * mom_coef[2])
norm = wp.length(mom_visc)
density = fluid_density / wp.max(wp.static(1e-10), norm)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets utilize MJ_MINVAL instead of wp.static(1e-10)

norm = wp.length(mom_visc)
density = fluid_density / wp.max(wp.static(1e-10), norm)

mom_sq = wp.vec3(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-density * wp.cw_mul(wp.cw_mul(ang, mom_coef), mom_coef)


proj_denom = aa * xx + bb * yy + cc * zz
proj_num = a * xx + b * yy + c * zz
dA_coef = wp.pi / wp.max(wp.static(1e-10), wp.sqrt(proj_num * proj_num * proj_num * proj_denom))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets utilize MJ_MINVAL

proj_num = a * xx + b * yy + c * zz
dA_coef = wp.pi / wp.max(wp.static(1e-10), wp.sqrt(proj_num * proj_num * proj_num * proj_denom))

A_proj = wp.pi * wp.sqrt(proj_denom / wp.max(wp.static(1e-10), proj_num))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets utilize MJ_MINVAL

A_proj = wp.pi * wp.sqrt(proj_denom / wp.max(wp.static(1e-10), proj_num))

norm = wp.sqrt(xx + yy + zz)
inv_norm = 1.0 / wp.max(wp.static(1e-10), norm)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets utilize MJ_MINVAL

xz, yz, zz + inner
)

D = D * (-quad_coef * inv_norm)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*=

proj_num = a * xx + b * yy + c * zz
norm2 = xx + yy + zz
df_denom = wp.pi * kutta_lift_coef * fluid_density / wp.max(
wp.static(1e-10), wp.sqrt(proj_denom * proj_num * norm2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MJ_MINVAL

dfx_coef = yy * (a - b) + zz * (a - c)
dfy_coef = xx * (b - a) + zz * (b - c)
dfz_coef = xx * (c - a) + yy * (c - b)
proj_term = proj_num / wp.max(wp.static(1e-10), proj_denom)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MJ_MINVAL

dfy_coef = xx * (b - a) + zz * (b - c)
dfz_coef = xx * (c - a) + yy * (c - b)
proj_term = proj_num / wp.max(wp.static(1e-10), proj_denom)
cos_term = proj_num / wp.max(wp.static(1e-10), norm2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MJ_MINVAL

proj_term = proj_num / wp.max(wp.static(1e-10), proj_denom)
cos_term = proj_num / wp.max(wp.static(1e-10), norm2)

D = wp.mat33(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a - a, b - b and c - c can be replaced with 0.0

a - b, b - b, c - b,
a - c, b - c, c - c
)
D = D * (wp.static(2.0) * proj_num)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*= and we can remove the parentheses

)
D = D * (wp.static(2.0) * proj_num)

inner_term = wp.vec3(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can utilize wp.cw_mul here

lin_vel_scaled = lin_vel * magnus_coef
ang_vel_scaled = ang_vel * magnus_coef

D_ang = _cross_deriv_a(ang_vel_scaled, lin_vel)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should lin_vel -> lin_vel_scaled?

ang_vel_scaled = ang_vel * magnus_coef

D_ang = _cross_deriv_a(ang_vel_scaled, lin_vel)
D_lin = _cross_deriv_b(ang_vel, lin_vel_scaled)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should ang_vel -> ang_vel_scaled?



@wp.func
def _cross_deriv_a(a: wp.vec3, b: wp.vec3) -> wp.mat33:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets consider combining _cross_deriv_a and _cross_deriv_b into one function that returns 2 wp.mat33 since it looks like we always compute derivatives wrt to both cross product inputs

Copy link
Collaborator

@thowell thowell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Aaryan-549 thank you for contributing this pr!

in addition to the comments, lets add a test. please let us know if you have any questions. thanks!

…J_MINVAL, restructure density checks, combine cross derivatives
@vector-one
Copy link
Contributor Author

Hey @thowell, really sorry for the delay. I have implemented all fixes that you asked for. Let me know If there are any other changes needed.

@erikfrey
Copy link
Collaborator

erikfrey commented Jan 5, 2026

@Aaryan-549 thank you for the contribution. It looks like there are some ruff linter issues, kernel analyzer issues, and unit test failures.

You should be able to run these three tools locally to reproduce and fix the errors reported in CI. Please give that a go - once the issues are fixed we'll take another look at the PR.

ruff and pytest issues should be straightforward. To install and use the kernel analyzer, see the readme here.

@vector-one
Copy link
Contributor Author

Thanks, apologies for the noise. I'll run the tools locally and fix the errors shortly.

@vector-one
Copy link
Contributor Author

I've run all three tools locally and fixed the issues.

Ruff formatting and linting both pass. I've fixed 21 failing tests and now have 925/926 tests passing. The one remaining failure (test_collision19) is a pre-existing collision bug that was already failing before my commits.

The kernel analyzer shows 3 style warnings in my code - two missing "# In:" comments and one parameter ordering convention issue. These don't break anything but I can fix them if you'd like, or address them in a follow-up after review.

@thowell
Copy link
Collaborator

thowell commented Jan 13, 2026

@Aaryan-549 thank you for the update!

all of the checks should pass including tests, formatting, and the linter. pre-commit will provide information about what should be fixed, please resolve any issues this tool reports.

thanks!

@thowell
Copy link
Collaborator

thowell commented Jan 13, 2026

we should also add a test to derivative_test.py that compares the result qDeriv_out from derive_ellipsoid_fluid to the mujoco mjData field qDeriv. note that the mujoco function mjd_ellipsoidFluid is not part of the python api so we will probably need to instead call mujoco.mj_forward to compute qDeriv. please let us know if you have any questions. thanks!

@SUZ-tsinghua
Copy link

@Aaryan-549 Please add forward test for the implicitfast integration for ellispoid fluid model as well. You can refer to #1022

@vector-one
Copy link
Contributor Author

Just did!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets remove the changes to this file since they are unrelated to the derivative feature and please submit a separate pr. thanks!

@thowell
Copy link
Collaborator

thowell commented Jan 15, 2026

@SUZ-tsinghua would you be open to contributing a review to this pr?

mujoco.mjtIntegrator.mjINT_IMPLICIT,
):
raise NotImplementedError(f"Implicit integrators and fluid model not implemented.")
# Removed check: Implicit integrators and fluid model are now supported
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the implicit integrator is not added in this pr we should keep this check

</mujoco>
"""
)
"""Tests that implicit integrator with fluid model is now supported."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can revert this change since the implicit integrator is not implemented in this pr

mjw.step(m, d)
self.assertGreater(d.time.numpy()[0], 0.0)

@parameterized.product(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the implicit integrator is not implemented in this pr we can remove this test

_assert_eq(mjw_out, mj_out, "qM - dt * qDeriv")

@parameterized.parameters(
(mujoco.mjtJacobian.mjJAC_DENSE, "ellipsoid"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to parameterized 'fluidshape' since both cases are ellipsoid

<site name="site2" pos="0 0 1"/>
</body>
</worldbody>
<tendon>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need tendons for this test?

<site site="site2"/>
</spatial>
</tendon>
<actuator>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need actuators for this test?

@SUZ-tsinghua
Copy link

Hi @thowell , I'd happy to review this pr. @Aaryan-549 For the derivative test, there is no need to include tendon and actuator. You can just copy my test.

@vector-one
Copy link
Contributor Author

Thanks @thowell! I've addressed all the latest feedback in the recent push:

  • Reverted changes to io.py, io_test.py, and forward_test.py. I agree it's better to keep the implicit integrator support for a separate PR.
  • Simplified derivative_test.py by removing unnecessary tendons, actuators, and the redundant fluidshape parameterization.
  • Removed unnecessary tendons and actuators from the test XML as suggested.
  • Reverted the unrelated formatting changes in kernel_analyzer.

Checks are passing. Ready for final review!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert all changes to this file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need any changes to this file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need any changes to this file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need any changes to this file

@vector-one vector-one force-pushed the feature/add-mjd-ellipsoidfluid-implementation branch 2 times, most recently from e618379 to 7ecd897 Compare January 23, 2026 19:19
@vector-one
Copy link
Contributor Author

Hey @thowell ,

I am really sorry for taking a whole week to reply back but I've been working on implementing the mjd_ellipsoidFluid derivative for PR #897, and I've run into a persistent issue that I could use some guidance on.

The Problem:
The test_smooth_vel_fluid tests are failing with ~50% of matrix elements mismatched. The diagonal elements match perfectly, but the off-diagonal elements have incorrect values. I've traced through the code extensively and believe the issue may be related to coordinate frame conventions or matrix storage order differences between MuJoCo and Warp.

What I've Verified/Fixed:

  • Integrator parameter type (changed from wp.array(dtype=int) to int)
  • Integrator enum check (using == 2 for IMPLICITFAST)
  • Dense vs sparse iteration (created separate kernels to iterate over all nv×nv DOF pairs for dense mode)
  • Attempted to match MuJoCo's column-major B matrix storage by transposing D in _add_to_quadrant

Current State:
The B matrix derivative functions (_added_mass_forces_deriv, _viscous_drag_deriv, etc.) appear to match MuJoCo's implementations, and the Jacobian computation follows the same approach. However, something about how the pieces fit together is producing wrong off-diagonal values.

Side Note:
I also noticed that when I ran @SUZ-tsinghua's original branch locally, there were 32 failing tests out of the box. Were the tests passing on your end when you reviewed? Just want to make sure I'm not chasing issues that existed before my changes.

Any pointers on what might be causing the mismatch would be really appreciated. Happy to hop on a call if that would be easier.

Thanks!

@SUZ-tsinghua
Copy link

Hi @Aaryan-549 , on my side, I only encountered 3 failed io tests on my branch. I have fixed these errors by merging the original branch into my branch.

@vector-one
Copy link
Contributor Author

Hey @SUZ-tsinghua even I am facing 3 failed tests on my end(for my code). Do you have any idea of what could be causing these?(we only need to take care of 2 as 1 of the test was failing in main too, so I don't think we caused it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add implementation of mjd_ellipsoidFluid

4 participants